#ifndef SLOTS_MEMBUF_H
#define SLOTS_MEMBUF_H

#include <cassert>
#include <memory>
#include <limits>
#include <algorithm>
#include <stdexcept>

#include "config.h"
#include "utility.h"

BEGIN_SLOTS_NS

class membuf
{
public:
	membuf()
		: p_(0), cap_sz_(0), rd_sz_(0), wr_sz_(0)
	{
	}

	virtual ~membuf()
	{
		this->clear();
	}

	size_t size()const
	{
		assert(wr_sz_ >= rd_sz_);
		return wr_sz_ - rd_sz_;
	}

	void * rd_ptr()
	{
		assert(p_ || (!p_ && !rd_sz_));
		return p_ + rd_sz_;
	}

	void * wr_ptr()
	{
		assert(p_ || (!p_ && !wr_sz_));
		return p_ + wr_sz_;
	}

	virtual void lock()
	{
	}

	virtual void unlock()
	{
	}

	virtual void reserve(size_t n)
	{
		if (this->space() >= n)
			return;

		if (cap_sz_ - this->size() >= n)
		{
			this->trim();
			return;
		}

		SLOTS_ASSERT(
			std::numeric_limits<size_t>::max() - this->size() >= n,
			std::length_error);
		this->reserve_i(n);
	}

	virtual void clear()
	{
		::free(p_);
		cap_sz_ = rd_sz_ = wr_sz_ = 0;
	}

	virtual void swap(membuf & mb)
	{
		std::swap(p_, mb.p_);
		std::swap(cap_sz_, mb.cap_sz_);
		std::swap(rd_sz_, mb.rd_sz_);
		std::swap(wr_sz_, mb.wr_sz_);
	}

	void write(const void * p, size_t n)
	{
		SLOTS_ASSERT(p, std::invalid_argument);
		this->reserve(n);
		::memcpy(this->wr_ptr(), p, n);
		wr_sz_ += n;
	}

	void read(void * p, size_t n)
	{
		SLOTS_ASSERT(p, std::invalid_argument);
		SLOTS_ASSERT(this->size() >= n, std::length_error);
		::memcpy(p, this->rd_ptr(), n);
		rd_sz_ += n;
	}

private:
	membuf(const membuf &);
	const membuf & operator=(const membuf &);

	size_t space()const
	{
		assert(cap_sz_ >= wr_sz_);
		return cap_sz_ - wr_sz_;
	}

	void trim()
	{
		if (p_ && rd_sz_)
		{
			::memcpy(p_, this->rd_ptr(), this->size());
			wr_sz_ = this->size();
			rd_sz_ = 0;
		}
	}

	void reserve_i(size_t n)
	{
		n += cap_sz_;
		char * p = static_cast<char *>(::malloc(n));
		if (!p)
			throw std::bad_alloc();

		if (p_)
		{
			::memcpy(p, p_, this->size());
			::free(p_);
		}

		p_ = p;
		cap_sz_ = n;
		wr_sz_ = this->size();
		rd_sz_ = 0;
	}

	char * p_;
	size_t cap_sz_;
	size_t rd_sz_;
	size_t wr_sz_;

}; // class membuf

END_SLOTS_NS

#endif
